# _*_coding:utf-8 _*_

from ESL import *

import os
import hashlib
import pymysql
from datetime import datetime
import threading
from threading import Timer
from urllib import parse
import urllib.parse
import xmltodict
import socket
import select
import json
import string
import time
import traceback
import random
from loguru import logger
# import requests

# 以下为本地模块
from mod import zybbx
from mod import tdzycdr
from mod import dthmd
from mod import hmdcall
from mod import dictss

# 以下为flask
# from gevent import monkey
# monkey.patch_all()  # 打上猴子补丁
from flask import Flask, request, jsonify, make_response, send_from_directory
from gevent import pywsgi
from flask_cors import CORS


print("********卓越TD-switch平台**********")

'''日志打印程序'''

logger.add('./log/TD_{time}.log', format="{time} {name} {level} {message}",
           level="INFO", rotation='10 MB', retention=10, encoding='utf-8')  # 根据时间为日志命名，每10MB新建一个,最大10个文件
logger.debug('调试消息')
logger.info('普通消息')
logger.warning('警告消息')
logger.error('错误消息')
logger.critical('严重错误消息')

'''
# 写程序进程id到pid文件
'''


def writepid():
    try:
        pid = str(os.getpid())
        f = open('conf/td.pid', 'w')
        f.write(pid)
        f.close()
    except Exception as ek:
        logger.error("写程序ID到pid错误:")
        logger.error(ek)
        logger.error(traceback.format_exc())


writepid()

'''全局用变量集'''
zypw = 'zy001'

# 计算话单个数和呼叫请求个数
xmlnub = {'n': 0}
cdrn = {'n': 0}
kmcn = {'n': 0}

'''mysql函数,传入sql命令，返回数据和状态，数据已按列表字典组合完成'''

# mysqlip = '101.132.128.114'
# mysqlip = '47.95.29.9'
mysqlip = '127.0.0.1'

# td外网ip地址全局变量

conf = {'tdnwip': '192.168.6.2', 'tdwwip': '8.131.53.111', 'tdjfcdr': 'https://cc.zy-yun.cn/VosCallBack.aspx'}

# tdwwip = '8.131.53.111'
# tdwwip = '47.96.19.213'
tdwwip = conf['tdwwip']


def zysql(sqlcmd, sqlname):
    conn = pymysql.connect(host=mysqlip,
                           user="root",
                           password="Dws666888",
                           port=3306,  # 端口
                           database=sqlname,
                           charset="utf8")
    cur = conn.cursor()
    try:
        # 执行SQL语句
        cur.execute(sqlcmd)
        desc = cur.description  # 获取字段的描述，默认获取数据库字段名称，重新定义时通过AS关键重新命名即可
        cur.close()
        conn.close()
        if desc:
            datac = [dict(zip([col[0] for col in desc], row)) for row in cur.fetchall()]  # 列表表达式把数据组装起来
            return datac
        else:
            return 0

    except Exception as sqlerr:
        cur.close()
        conn.close()
        logger.error("zysql错误:")
        logger.error(sqlerr)
        logger.error(traceback.format_exc())
        return 0


'''自定义ip查询'''


def zysqlip(sqlcmd, sqlname, sqlip):
    conn = pymysql.connect(host=sqlip,
                           user="root",
                           password="Dws666888",
                           port=3306,  # 端口
                           database=sqlname,
                           charset="utf8")
    cur = conn.cursor()
    try:
        # 执行SQL语句
        cur.execute(sqlcmd)
        desc = cur.description  # 获取字段的描述，默认获取数据库字段名称，重新定义时通过AS关键重新命名即可
        cur.close()
        conn.close()
        if desc:
            datac = [dict(zip([col[0] for col in desc], row)) for row in cur.fetchall()]  # 列表表达式把数据组装起来
            return datac
        else:
            return 0

    except Exception as sqlerr:
        cur.close()
        conn.close()
        logger.error("zysql错误:")
        logger.error(sqlerr)
        logger.error(traceback.format_exc())
        return 0


'''插入、删除用sql函数'''


def zysqls(sqlcmd, sqlname):
    conn = pymysql.connect(host=mysqlip,
                           user="root",
                           password="Dws666888",
                           port=3306,  # 端口
                           database=sqlname,
                           charset="utf8")
    cur = conn.cursor()
    try:
        # 执行SQL语句
        cur.execute(sqlcmd)
        conn.commit()
        cur.close()
        conn.close()
        return 1

    except Exception as sqlerr:
        conn.commit()
        cur.close()
        conn.close()
        logger.error("zysql错误:")
        logger.error(sqlerr)
        logger.error(traceback.format_exc())
        return 0


'''获取总条数函数'''


def zysqlnum(sqlcmd, sqlname):
    conn = pymysql.connect(host=mysqlip,
                           user="root",
                           password="Dws666888",
                           port=3306,  # 端口
                           database=sqlname,
                           charset="utf8")
    cur = conn.cursor()
    try:
        cur.execute(sqlcmd)
        desc = cur.description  # 获取字段的描述，默认获取数据库字段名称，重新定义时通过AS关键重新命名即可
        datac = [dict(zip([col[0] for col in desc], row)) for row in cur.fetchall()]  # 列表表达式把数据组装起来
        cur.execute("SELECT FOUND_ROWS() as total")
        results = cur.fetchall()

        # cur.fetchone()
        # nub = cur.rowcount  # 条数
        cur.close()
        conn.close()
        print(desc)
        print(results)
        nub = 0
        if results:
            nub = results[0][0]

        return datac, nub
    except Exception as sqlerr:
        cur.close()
        conn.close()
        logger.error("zysqlnum错误:")
        logger.error(sqlerr)
        logger.error(traceback.format_exc())
        return 0


'''获取单个值  sql函数'''


def zysqld(sqlcmd, sqlname):
    conn = pymysql.connect(host=mysqlip,
                           user="root",
                           password="Dws666888",
                           port=3306,  # 端口
                           database=sqlname,
                           charset="utf8")
    cur = conn.cursor()

    # values = '0'
    try:
        # 执行SQL语句
        cur.execute(sqlcmd)
        results = cur.fetchall()
        cur.close()
        conn.close()
        # logger.warning({'单条查到的值': results})
        if results:
            for row in results:
                values1 = row[0]
                values2 = row[1]
                return values1, values2, row[2], row[3], row[4]
        else:
            return 0, 0, 0, 0, 0

    except Exception as sqlerr:
        conn.commit()
        cur.close()
        conn.close()
        logger.error("zysql错误:")
        logger.error(sqlerr)
        logger.error(traceback.format_exc())
        return 0, 0, 0, 0, 0


'''
# 定义appid
appid     apipassword
{'zy001':{'appid':'zy001','sid':'123','token':'123abc','secret':'123abc','time': 1627979021.5405989},'umail':'', 'attribution':''}

'''

'''

with open('conf/appid.txt', 'r') as appid1:
    appid1 = appid1.read()
zyappid = eval(appid1)
# print(zyappid)
# print(type(zyappid))
'''

zyappid = {
    'zy001': {'token': '123abc', 'pw': 'zzzz9999', 'sip': 'zy1000', 'time': 1627979021.5405989},
    'zysys01': {'token': '123abc', 'pw': 'zzzz9999', 'sip': 'zy1000', 'time': 1627979021.5405989},
}


'''黑名单规则3次24小时限制，通用'''

hmdgz324 = {'callstate': 'all', 'nub': '3', 'date': '1', 'starttime': '08:00:00', 'endtime': '18:00:00'}

'''
# 定义坐席token
appid     apipassword
{'zy001':{'appid':'zy001','sid':'123','token':'123abc','secret':'123abc','time': 1627979021.5405989},}

'''

'''
agent_tk = {
    '1000@zyun': {'token': '123abc', 'pw': 'zzzz9999', 'sip': '1000', 'time': 1627979021.5405989},

}
'''
agent_tk = {
    'dws@zy': {'token': '123abc', 'pw': 'zzzz9999', 'sip': 'zy1000', 'time': 1627979021.5405989},
    'fzh@zy': {'token': '123abc', 'pw': 'zzzz9999', 'sip': 'zy1001', 'time': 1627979021.5405989},
    'sgg@zy': {'token': '123abc', 'pw': 'zzzz9999', 'sip': 'zy1002', 'time': 1627979021.5405989},
    'xy@zy': {'token': '123abc', 'pw': 'zzzz9999', 'sip': 'zy1003', 'time': 1627979021.5405989},
}

'''
def loadagent_tk():
    # 执行SQL语句
    sqla = "select * from users"
    sqlip = '101.132.128.114'
    datac = zysqlip(sqla, 'crmdatacc', sqlip)

    for k in datac:
        agent_tk[k['userName']] = k
    # print(agent_tk)


loadagent_tk()
'''


'''
对接和落地路由


网关id     网关id          主叫ip               主叫前缀           被叫前缀及前缀优先级    主叫前缀改写          被叫前缀改写         网关                  网关类型 j静态 d动态，状态 y开启 n关闭 
{'zy010':{'gwid':'zy010','fromip':['123.0.0.1', '192.168.0.142'],'caller':'123abc','called':'123abc','gcaller':'123abc','gcalled':'123abc',

网关                  网关类型 j静态 d动态，状态 y开启 n关闭  呼叫帐号                    落地帐号                                 网关名           黑名单id        
'gw':'123.0.0.0:5688','type':'j','state':'y','inappid':'zy001','inappname':'卓越','outappid':'zy002','outappname':'浩驰'，'gwname':'测试1','hmdid': ''}

{
    'lyzy1': {'lyid': 'lyzy1', 'fromip': ['123.0.0.1', '192.168.31.142'], 'caller': ['123abc', '1003', ],
              'called': '123abc', 'gwlist': {'zy010': 2, 'gw2': 1}, 'state': 'y', 'inappid': 'zy001',
              'inappname': '卓越', 'lyname': '测试', 'hmdid': '', 'luyin': 'n', 'meiti': 'y', 'bf': '', 'gxuid': 'sngx'},
}

outgw = {
    'zy010': {'gwid': 'zy010', 'gcaller': {'1003': [0, '0101/0102/0103'], }, 'gcalled': {'123abc': '9196', },
              'gwip': '47.96.19.213', 'gwport': '56889', 'type': 'j', 'outappid': 'zy002', 'outappname': '浩驰',
              'gwname': '测试',
              },
    'gw2': {'gwid': 'zy010', 'gcaller': {'1003': [0, '0101/0102/0103'], }, 'gcalled': {'123abc': '9196', },
            'gwip': '192.168.31.142', 'gwport': '5688', 'type': 'j', 'outappid': 'zy002', 'outappname': '浩驰',
            'gwname': '测试',
            },

}
'''

# inly = {}

# 打开luyou文件读取inly赋值给字典inly

with open('conf/luyou.txt', 'r', encoding='utf-8') as lytxt:
    lytxt = lytxt.read()

inly = eval(lytxt)

print(inly)
print(type(inly))
print(type(inly['lyzy1']['fromip']))

# 打开outgw文件读取outgw赋值给字典outgw

with open('conf/outgw.txt', 'r', encoding='utf-8') as gw1txt:
    gw1txt = gw1txt.read()

outgw = eval(gw1txt)

print(outgw)
print(type(outgw))

'''
并发监控


网关id     网关id          主叫ip               主叫前缀           被叫前缀及前缀优先级    主叫前缀改写          被叫前缀改写         网关                  网关类型 j静态 d动态，状态 y开启 n关闭 
{'zy010':{'gwid':'zy010','fromip':['123.0.0.1', '192.168.0.142'],'caller':'123abc','called':'123abc','gcaller':'123abc','gcalled':'123abc',

网关                  网关类型 j静态 d动态，状态 y开启 n关闭  呼叫帐号                    落地帐号                                 网关名           黑名单id        
'gw':'123.0.0.0:5688','type':'j','state':'y','inappid':'zy001','inappname':'卓越','outappid':'zy002','outappname':'浩驰'，'gwname':'测试1','hmdid': ''}



'''

calllist = {
    'laksfke24029jf': {'callid': '666', 'starttime': '2022-06-09 11:13:38', 'caller': '666', 'called': '666',
                       'lyid': 'zy010', 'lyname': '测试',
                       'callstate': '1', 'awtime': '2022-06-09 11:13:39', 'gwlist': {'zy010': 2, 'gw2': 1}, 'mt': '',
                       'fromip': '123.123.123.0', 'switchname': 'freeswitch', 'jg': '5'},
}

'''黑名单'''

hmd = {
    'zy001': ['183', ]
}

'''对应路由（对接网关）实时并发统计'''


def lybfcall(lyid):
    try:
        calldata = calllist
        nub = 0
        for c in calldata:
            if calldata[c]['lyid'] == lyid:
                nub = nub + 1
        return nub
    except Exception as ek:
        logger.error("实时并发统计错误")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return 0



'''路由匹配模块'''


def lyauth(fromip, caller, called):
    try:
        lydata = inly
        jg = 0
        for l in lydata:
            if fromip in lydata[l]['fromip'] and lydata[l]['state'] == 'y':  # 判断ip和路由是否属于开通状态
                # print('ip地址和路由状态已验证通过')
                if jg < 1:
                    jg = 1
                if caller in lydata[l]['caller'] or '*' in lydata[l]['caller']:  # 主叫完全匹配或配置有*号为全部通过
                    # print('主叫号码已验证通过')
                    if jg < 2:
                        jg = 2
                    if [i for i in lydata[l]['called'].split('/') if called.startswith(i)]:  # 匹配被叫，多个被叫配置可以用/分隔
                        # print('被叫验证通过')
                        if jg < 3:
                            jg = 3
                        if lydata[l]['hmdid'] and \
                                ((lydata[l]['hmdid'].split('/')[0] != 'dt' and hmdcall.hmdpd(called[3:], lydata[l]['hmdid'], hmdgz324) == 'lj') or
                                 (lydata[l]['hmdid'].split('/')[0] == 'dt' and
                                  dthmd.hmd(lydata[l]['hmdid'].split('/')[1], called[3:]) == 'lj')):
                            # print('黑名单,先判断hmdid有值，如有，判断自有黑名单和动态黑名单，有一个为真就拦截')
                            return jg, 0, l

                        elif lybfcall(l) >= int(inly[l]['bf']):
                            # print('并发超限')
                            jg = 4
                            return jg, lydata[l]['gwlist'], l
                        else:
                            print('通过')
                            jg = 5
                            return jg, lydata[l]['gwlist'], l
                    # else:
                    # print('被叫未通过')
                # else:
                # print('主叫未通过')
            # else:
            # print('ip未通过')
        return jg, 0, 0
    except Exception as ek:
        logger.error("路由验证错误")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return 0, 0, 0


'''fs呼叫返回xml组合函数'''

# 数据变量


callrd = '<document type="freeswitch/xml">' \
         '<section name="dialplan" description="RE Dial Plan For FreeSwitch">' \
         '<context name="default">' \
         '<extension name="aaa9">' \
         '<condition field="destination_number" expression="^a102">' \
         '<action application="answer"/>' \
         '<action application="echo"/>' \
         '</condition>' \
         '</extension>' \
         '</context>' \
         '</section>' \
         '</document>'

# fscallxml头部
callxml1 = '<document type="freeswitch/xml">' \
           '<section name="dialplan" description="RE Dial Plan For FreeSwitch">'

# callxmltype = '<context name="default">'
# callerxml = '<condition field="caller_id_number" expression="^(8080[0-3][0-9])$"/>'
# calledxml = '<condition field="destination_number" expression="^a102">'

# 开启通话录音
luyinxml = '<action application="set" data="RECORD_TITLE=Recording ${destination_number} ${caller_id_number} ${strftime(%Y-%m-%d %H:%M)}"/>' \
           '<action application="set" data="RECORD_COPYRIGHT=(c) 1980 Factory Records, Inc."/>' \
           '<action application="set" data="RECORD_SOFTWARE=FreeSWITCH"/>' \
           '<action application="set" data="RECORD_ARTIST=Ian Curtis"/>' \
           '<action application="set" data="RECORD_COMMENT=Love will tear us apart"/>' \
           '<action application="set" data="RECORD_DATE=${strftime(%Y-%m-%d %H:%M)}"/>' \
           '<action application="set" data="RECORD_STEREO=true"/>' \
           '<action application="set" data="media_bug_answer_req=true"/>' \
           '<action application="record_session" data="$${sounds_dir}/recordings/${strftime(%Y-%m-%d)}/${strftime(%Y-%m-%d-%H-%M-%S)}_${destination_number}_${caller_id_number}.wav"/>'

# 不过媒体，只走信令
nomeiti = '<action application="set" data="bypass_media=true"/>'

# 桥接demo

# bjdata = '<action application="bridge" data="{origination_caller_id_number=5cxz}sofia/external/837$1@47.96.19.213:56889"/>'
# bddata = '<action application="bridge" data="{sip_invite_to_uri=sip:$1@47.96.19.213}user/xlxsy01"/>'

# 结尾
endxml = '</condition>' \
         '</extension>' \
         '</context>' \
         '</section>' \
         '</document>'

'''fsxml的notfound'''

notf = '<document type="freeswitch/xml">' \
       '<section name="result">' \
       '<result status="not found" />' \
       '</section>' \
       '</document>'

'''轮询选号函数'''


def lxqh(haomalist, wz):
    wzx = ''
    if wz < len(haomalist) - 1:
        wzx = wz + 1
    else:
        wzx = len(haomalist) - wz - 1
    return wzx


'''随机字典排序函数，权重为一样的可以随机选择，相对平均些'''


def random_dic(dicts):
    print(dicts)
    print(type(dicts))
    dict_key_ls = [d for d in dicts]
    random.shuffle(dict_key_ls)
    new_dict = {}
    for key in dict_key_ls:
        new_dict[key] = dicts.get(key)
    return new_dict


'''fs-call-xml合成函数'''


def fscall(gwlist, inlyid, caller, called, typecall):
    try:
        nametime = str(time.time())
        callxmltype = '<context name="' + typecall + '">' \
                                                 '<extension name="zy' + nametime + '">'
        callerxml = '<condition field="caller_id_number" expression="^(' + caller + ')$"/>'
        calledxml = '<condition field="destination_number" expression="^' + called + '">'

        # 加自定义变量，故障转移，故障码
        setxml = '<action application="set" data="zysip=${caller_id_number}"/>' \
                 '<action application="set" data="lyid=' + inlyid + '"/>' \
                                                                    '<action application="set" data="hangup_after_bridge=true"/>' \
                                                                    '<action application="set" data="continue_on_fail=true"/>' \
                                                                    '<action application="set" data="continue_on_fail=1,2,3,21,88,41,42,44,58,18,34,606,96"/>'
        bridgexml = ''

        # 把网关列表把从小到大排序，数字越小优先级越高
        newgw = random_dic(gwlist)  # 把字典里的gw排序随机下，同等权重的可以随机排序，以达到尽量平均
        gwpx = sorted(newgw.items(), key=lambda x: x[1])
        gwl = [k[0] for k in gwpx]

        # for循环组合多个桥接，按规则改写主被叫号码
        for x in gwl:
            if x in outgw:

                gw = outgw[x]
                gcaller = gw['gcaller']
                gcaller1 = gcaller.get(caller)
                gcalled = gw['gcalled']

                ghcaller = ''
                ghcalled = ''
                # 开始改写主叫
                if gcaller1:
                    haomalist = gcaller1[1].split('/')
                    ghcaller = haomalist[gcaller1[0]]
                    # 取用后更新位置为下一个号码
                    outgw[x]['gcaller'][caller][0] = lxqh(haomalist, gcaller1[0])
                else:
                    # 如果有*号的主叫号码改写，在没有匹配到主叫时，选择*的主叫改写，为空时不改写
                    allg = gcaller.get('*')
                    if allg:
                        haomalist = allg[1].split('/')
                        ghcaller = haomalist[allg[0]]
                        # 取用后更新位置为下一个号码
                        outgw[x]['gcaller']['*'][0] = lxqh(haomalist, allg[0])

                # 开始被叫号码改写
                for g in gcalled:
                    if called.startswith(g):  # 如果被叫改写里与被叫号码可以在前几位匹配上
                        # 按被叫改写前缀位数改写，新前缀加上去掉老前缀位数的被叫号码，组合成新被叫
                        ghcalled = gcalled[g] + called[(len(g)):]

                # 判断经过上面路由，是否有改写，如无改写，用原主叫或被叫，如有就用改写后的
                cer = ''
                ced = ''
                if ghcalled:
                    ced = ghcalled
                else:
                    ced = called
                if ghcaller:
                    cer = ghcaller
                else:
                    cer = caller
                # 判断网关类型，j为静态，d为动态，走不同的模版
                gwtype = gw.get('type')
                gwip = gw.get('gwip')
                gwport = gw.get('gwport')

                if gwtype == 'j':
                    bridgexml = bridgexml + '<action application="set" data="gwid=' + x + '"/>'+'<action application="bridge" data="{origination_caller_id_number=' + cer + '}sofia/external/' + ced + '@' + gwip + ':' + gwport + '"/>'
                    bridgexml = bridgexml + '<action application="python" data="tdtime"/>'  # 增加多路由跳转判断，距离开始呼叫时间超过2秒就不在转其它路由

                elif gwtype == 'd':
                    bridgexml = bridgexml + '<action application="set" data="gwid=' + x + '"/>'+'<action application="bridge" data="{sip_invite_to_uri=sip:' + ced + '@' + gwip + '}user/' + gwport + '"/>'
                    bridgexml = bridgexml + '<action application="python" data="tdtime"/>'  # 增加多路由跳转判断，距离开始呼叫时间超过2秒就不在转其它路由
            else:
                logger.warning({'无此网关，不做此网关合成': x})
        # 生成最终要返回给fs的xml
        rcallxml = ''
        luyin = inly[inlyid]['luyin']
        if luyin == 'y':
            rcallxml = callxml1 + callxmltype + callerxml + calledxml + luyinxml + setxml + bridgexml + endxml
        else:
            rcallxml = callxml1 + callxmltype + callerxml + calledxml + setxml + bridgexml + endxml
        return rcallxml
    except Exception as ek:
        logger.error("callxml合成与改写错误")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return 0


'''
呼叫鉴权入库、通话监控存入异步处理执行线程函数
'''


class jkcdrThread(threading.Thread):
    def __init__(self, threadID, name, data):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.data = data

    def run(self):
        try:
            logger.info("开始异步监控和入库线程：" + self.name)
            jkandcdr(self.data)
        except Exception as e:
            logger.error("异步监控和入库线程错误:")
            logger.error(e)
            logger.error(traceback.format_exc())


'''监控和呼叫请求异步入库'''


def jkandcdr(data):
    try:
        logger.warning({'收到异步处理线程调用': data})
        callid = data.get('callid', '0')
        caller = data.get('caller', '0')
        called = data.get('called', '0')
        callip = data.get('callip', '0')
        evtime = data.get('evtime', '0')
        inappid = data.get('inappid', '')
        lyid = data.get('lyid', '0')
        gcaller = data.get('gcaller', '0')
        lyname = ''
        if lyid:
            lyname = inly[lyid]['lyname']
        gwlist = data.get('gwlist')
        mt = data.get('meiti')
        switchname = data.get('switchname')
        jg = data.get('jg')

        # 写入监控calllist
        calllist[callid] = {'callid': callid, 'starttime': evtime, 'caller': caller, 'called': called, 'lyid': lyid,
                            'lyname': lyname, 'callstate': 'ringing', 'awtime': '', 'gwlist': gwlist,
                            'mt': mt, 'fromip': callip, 'switchname': switchname, 'jg': jg, }
        # cdr请求入库 fzhsql
        # sqlzd = "insert into tdcdr (callid,starttime,caller,called,lyid,lyname,callstate,gwlist,mt,fromip,switchname,jg,caller_user) values" ('" + str(callid) + "','" + str(evtime) + "','" + str(caller) + "','" + str(called) + "','" + str(lyid) + "','"\
        # + lyname + "','ringing','"\
        # + str(gwlist) + "','" + str(mt) + "','" + str(callip) + "','" + switchname + "','" + str(jg) + "')"
        # logger.error('sqlzd:'+sqlzd)
        valu1 = (
        callid, evtime, caller, called, lyid, lyname, 'ringing', str(gwlist), mt, callip, switchname, jg, gcaller, inappid)
        sqlzd = "insert into tdcdr (callid,starttime,caller,called,lyid,lyname,callstate,gwlist,mt,fromip,switchname,jg,caller_user,inappid)" \
                " values " + str(valu1)
        zysqls(sqlzd, 'crmdatacc')

    except Exception as e:
        logger.error("异步监控和入库错误:")
        logger.error(e)
        logger.error(traceback.format_exc())


'''定时查询mysql，查km的接听、挂断、未接事件，更新监控和cdr话单'''


# 线程异步定时程序
def time_sqlkm(inc):
    kmsql()
    t = Timer(inc, time_sqlkm, (inc,))
    t.start()


def kmsql():
    try:

        logger.warning('开始定时查sql')
        sqlname = 'kamailio'  # 数据库名称
        sqlname2 = 'crmdatacc'  # 数据库名称
        curtime1 = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
        # 查询近3小时内state为0的成功接听表 ACC成功接听有
        sqlcc = "select * from acc where state='0' and time >= DATE_ADD('" + curtime1 + "', INTERVAL -3 HOUR)"
        datac = zysql(sqlcc, sqlname)

        if datac:  # 查到数据才改写
            # 改写查到的数据state为1
            # sqlcg = "update acc set state='1' where state='0' and callid in (select callid from acc where state='0' and time >= DATE_ADD('" + curtime1 + "', INTERVAL -3 HOUR))"
            # zysqls(sqlcg, sqlname)
            lbcallid = ['0']
            for c in datac:
                lbcallid.append(c['id'])
            sqlcg = "update acc set state='1' where state='0' and id in " + str(tuple(lbcallid))
            zysqls(sqlcg, sqlname)
        if datac:

            # 执行查询，拿到datac，处理datac数据，修改呼叫监控和cdr
            for k in datac:
                if k['method'] == 'INVITE':
                    callid = k['callid']
                    awtime = str(k['time'])  # 从数据库返回的数据里取time
                    # 修改监控里为接听
                    if callid in calllist:
                        calllist[callid]['callstate'] = 'answer'
                        calllist[callid]['awtime'] = awtime
                    # 修改mysql里的cdr接听时间 fzhsql
                    sqljg = "update tdcdr set awtime='" + awtime + "',callstate='answer' where callid='" + str(
                        callid) + "'"
                    # logger.warning({'查到数据，把接听时间加到库里': awtime})
                    zysqls(sqljg, sqlname2)
                elif k['method'] == 'BYE':
                    callid = k['callid']
                    endtime = str(k['time'])  # 从数据库返回的数据里取time
                    # 删除监控里对应的呼叫，因通话已挂断
                    hm = ''
                    hmdidj = ''
                    if callid in calllist:
                        hm = calllist[callid]['called']
                        hmdidj = inly[calllist[callid]['lyid']]['hmdid']
                        del calllist[callid]
                    # 修改mysql里的cdr挂断时间，并计算通话时长和计费时长 fzhsql
                    sqljt = "select starttime,awtime,caller,called,inappid from tdcdr where id>0 and callid='" + str(callid) + "'"
                    starttimea, awtimes, caller, called, inappid = zysqld(sqljt, sqlname2)
                    # logger.warning('接听时间awtimes:' + awtimes)
                    alltime = 0
                    jttime = 0
                    thsc = 0
                    if awtimes:
                        # 开始时间转为时间戳
                        starttimeaArray = time.strptime(starttimea, "%Y-%m-%d %H:%M:%S")
                        starttimeaStamp = int(time.mktime(starttimeaArray))
                        # 接听时间转为时间戳
                        awtimeArray = time.strptime(awtimes, "%Y-%m-%d %H:%M:%S")
                        awtimeStamp = int(time.mktime(awtimeArray))
                        # 挂断时间转为时间戳
                        endtime1 = time.strptime(endtime, "%Y-%m-%d %H:%M:%S")
                        endtimeStamp = int(time.mktime(endtime1))
                        # 时间戳相减得总时长
                        alltime = int(endtimeStamp - starttimeaStamp)  # 总时长
                        # 时间戳相减得通话时长
                        jttime = int(endtimeStamp - awtimeStamp)  # 通话时长

                        thsc = int(jttime / 60) + 1  # 计费时长

                    sqljg = "update tdcdr set endtime='" + endtime + "',alltime=" + str(alltime) + ",jttime=" + str(
                        jttime) + ",jftime=" + str(thsc) + ",sipcode='" + k['sip_code'] + "',outgw='" + str(
                        k['src_domain']) + "',caller_user='" + str(k['dst_ouser']) + "',called_user='" + str(
                        k['src_user']) + "'  where callid='" + callid + "'"
                    logger.warning({'挂断事件，计算时长并入库sqljg:': sqljg})
                    zysqls(sqljg, sqlname2)
                    # 如果appid在列表里，要推送到老系统计费
                    if inappid in ['h61gy3']:
                        rdata = {'starttime': starttimea, 'endtime': endtime, 'caller': caller, 'called': called,
                                 'inappid': inappid, 'answer_stamp': awtimes, 'billsec': jttime, 'callid': callid,
                                 'totaltime': alltime}
                        cdrurl = conf.get('tdjfcdr')  # 需要接收的话单地址
                        rdata['cdrurl'] = cdrurl
                        # rdata['lyname'] = lyname
                        # rdata['inappid'] = inappid
                        tdzycdr.cdrt(rdata)  # 执行推送


                    # 调用hmd记录函数，处理呼叫
                    if hm and hmdidj:
                        print('现在开始加入黑名单')
                        hmdcall.hmdsc({'called': hm, 'state': '接听', 'endtime': endtime}, hmdgz324, hmdidj)

        else:
            print('acc暂无新数据')

        # 查询近3小时内state为0的未接听表 missed_calls表
        sqls = "select * from missed_calls where state='0' and time >= DATE_ADD('" + curtime1 + "', INTERVAL -3 HOUR)"
        datas = zysql(sqls, sqlname)
        if datas:  # 查到数据才执行改写
            # 改写查到的数据state为1
            # sqlsg = "update missed_calls set state='1' where state='0' and callid in (select callid from missed_calls where state='0' and time >= DATE_ADD('" + curtime1 + "', INTERVAL -3 HOUR))"
            # zysqls(sqlsg, sqlname)
            lbcallids = ['0']
            for cs in datas:
                lbcallids.append(cs['id'])
            sqlsg = "update missed_calls set state='1' where state='0' and id in " + str(tuple(lbcallids))
            # logger.error('sqlsg:' + str(sqlsg))
            zysqls(sqlsg, sqlname)

        if datas:
            # 执行查询，拿到datas，改写查到的数据state为1，处理datac数据，修改呼叫监控和cdr
            # logger.warning({'查到missid_calls表数据': datas})
            for d in datas:
                callid = d['callid']
                endtime = str(d['time'])  # 从数据库返回的数据里取time
                # 删除呼叫监控里对应的呼叫
                hm = ''
                hmdidj = ''
                if callid in calllist:
                    try:
                        if calllist.get(callid).get('lyid'):
                            hm = calllist[callid]['called']
                            hmdidj = inly[calllist[callid]['lyid']]['hmdid']
                        del calllist[callid]
                    except Exception as em:
                        logger.warning({'删除call监控和获取hmdid错误:': em})

                # 修改cdr,更新挂断时间(time)、挂断原因(sip_code)、最终gwip(des_domain),计算总时长 tdcdr
                sip_code = d['sip_code']  # 挂断原因
                sqlks = "select starttime,starttime,caller,called,inappid from tdcdr where id>0 and callid='" + str(callid) + "'"
                starttimes, starttimes2, caller2, called2, inappid = zysqld(sqlks, sqlname2)
                # logger.error('starttimes:' + starttimes)
                # logger.error('time:'+ str(endtime))

                alltime = 0
                if starttimes:
                    # 开始时间转为时间戳
                    sttimeArray = time.strptime(starttimes, "%Y-%m-%d %H:%M:%S")
                    sttimeStamp = int(time.mktime(sttimeArray))

                    # 结束时间转为时间戳
                    endtime1 = time.strptime(endtime, "%Y-%m-%d %H:%M:%S")
                    endtimeStamp = int(time.mktime(endtime1))

                    # 时间戳相减得总时长
                    alltime = int(endtimeStamp - sttimeStamp)
                sqlzd = "update tdcdr set endtime='" + str(endtime) + "',sipcode='" + sip_code + "',alltime=" + str(
                    alltime) + ",outgw='" + str(d['dst_domain']) + "',called_user='" + str(d['dst_user']) \
                        + "' where callid='" + str(callid) + "'"
                # logger.error('sqlzd:' + str(sqlzd))
                zysqls(sqlzd, sqlname2)
                # logger.warning({'收到呼叫失败话单，现入库到cdr': sqlzd})

                # 如果appid在列表里，要推送到老系统计费
                if inappid in ['h61gy3']:
                    rdata = {'starttime': starttimes, 'endtime': str(endtime), 'caller': caller2, 'called': called2,
                             'inappid': inappid, 'answer_stamp': '', 'billsec': '', 'callid': callid,
                             'totaltime': alltime}
                    cdrurl = conf.get('tdjfcdr')  # 需要接收的话单地址
                    rdata['cdrurl'] = cdrurl
                    # rdata['lyname'] = lyname
                    # rdata['inappid'] = inappid
                    tdzycdr.cdrt(rdata)  # 执行推送

                # 调用hmd记录函数，处理呼叫
                if hm and hmdidj:
                    print('现在开始加入黑名单')
                    hmdcall.hmdsc({'called': hm, 'state': '接听', 'endtime': endtime}, hmdgz324, hmdidj)
        else:
            print('missed_calls暂无新数据')
    except Exception as e:
        logger.error("定时查库入库错误:")
        logger.error(e)
        logger.error(traceback.format_exc())


time_sqlkm(5)  # 执行定时，每5秒查一次数据库

'''

ip未通过 0
主叫未通过 1
被叫未通过 2
黑名单未通过 3
并发超限 4
通过 5
'''

'''
************************
fs-esl连接实时监听事件模块
***********************
'''

'''fs连接信息'''

# 从文件获取要连接的fs服务器信息
hostIp = '127.0.0.1'
fsport = '9088'
fsuser = 'Zyunfs'

scon = ESLconnection(hostIp, fsport, fsuser)

'''socket信息'''

# 创建 socket 对象
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取本地主机名
host = conf['tdnwip']

# 设置端口号
port = 8448

'''连接状态'''
econ = {'con': scon}
jkv = {'a': 1}  # fs连接状态
ks = {'a': 0}  # kmsoc连接状态

kmstate = {'s': ''}  # km的soc连接属性映射

'''定时监控连接状态函数'''


def time_fs(inc):
    try:
        jkfsh()
        t = Timer(inc, time_fs, (inc,))
        t.start()

    except Exception as e:
        logger.error("fs连接监控定时错误:")
        logger.error(e)
        logger.error(traceback.format_exc())


def jkfsh():
    try:
        con = econ['con']
        kst = ks['a']
        if con.connected():
            logger.warning('*****esl连接fs成功状态')
        else:
            jkv['a'] = 0
            logger.warning('******esl连接fs失败，正在重连......')
            #  con.disconnect()
            dcon = ESLconnection(hostIp, fsport, fsuser)
            if dcon.connected():
                econ['con'] = dcon
                logger.warning('*****esl连接fs成功状态')
                jkv['a'] = 1
                ran_str = random.randrange(1, 100000, 1)
                nowtime = datetime.now().strftime("%Y%m%d%H%M%S")
                ran = str(ran_str) + nowtime
                threadjk1 = jkThread(int(ran), ran, int(ran))
                threadjk1.start()
                logger.info('监控进程重新开始：' + ran)
        if kst:
            logger.warning('kmsoc连接成功状态')
        else:
            logger.warning('kmsoc连接已断开，正在重连.....')
            # kmsoc()  # 执行连接kmsoc
            # ks['a'] = 1  # 更新连接状态
            # print('kmsoc重连成功')
            ran_str = random.randrange(1, 100000, 1)
            nowtime = datetime.now().strftime("%Y%m%d%H%M%S")
            ran = str(ran_str) + nowtime
            threadkm1 = kmThread(int(ran), ran, int(ran))
            threadkm1.start()
            logger.info('kmsoc监控进程重新开始：' + ran)

    except Exception as ek:
        logger.error("fs连接和kmsoc监控错误:")
        logger.error(ek)
        logger.error(traceback.format_exc())


'''  监控fs-esl连接线程函数   '''


class fsThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        # logger.warning("开始fs监控线程：" + self.name)
        time_fs(10)
        logger.error("退出fs监控线程：" + self.name)


''' ***写文件线程函数*** '''


class xwjThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        logger.info("开始写入文件线程：" + self.name)
        time_w(10)
        # logger.error("退出线程：" + self.name)


'''
# 监控事件线程函数，接收通话事件time_dtime
'''


class jkThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        try:
            logger.info("开始事件线程：" + self.name)
            jiank(1)
            logger.error("退出事件线程：" + self.name)
        except Exception as e:
            logger.error("事件监控错误:")
            logger.error(e)
            logger.error(traceback.format_exc())


'''
# 监控事件分线程函数，处理通话事件
'''


class jkfThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        try:
            logger.info("开始通话事件处理线程：" + self.name)
            jiankf(self.counter)
            logger.info("退出通话事件线程：" + self.name)
        except Exception as e:
            logger.error("事件监控分错误:")
            logger.error(e)
            logger.error(traceback.format_exc())


'''
# kmsoc事件实时接收线程函数
'''


class kmThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        try:
            logger.info("开始事件线程：" + self.name)
            kmevt(1)
            logger.error("退出事件线程：" + self.name)
        except Exception as e:
            logger.error("事件监控错误:")
            logger.error(e)
            logger.error(traceback.format_exc())


'''  多线程分处理km事件   '''


class kmevthd(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        logger.warning("开始km事件处理线程：" + self.name)
        kmev(self.counter)
        logger.error("退出km事件处理线程：" + self.name)


'''
# 定期将字典写回文件函数，以防止程序中断或重启后丢失
'''


def time_w(inc):
    writetxt()

    t = Timer(inc, time_w, (inc,))
    t.start()


def writetxt():
    with open('conf/luyou.txt', 'w+') as cc_ly1:
        cc_ly1.write(str(inly))
    # with open('conf/call_queue.txt', 'w+') as call_queue1:
    #    call_queue1.write(str(zycall_queue))
    with open('conf/outgw.txt', 'w+') as cc_gw:
        cc_gw.write(str(outgw))
    logger.warning('文件写入成功')


'''***座席状态实时监控更新***'''


def jiank(jk):
    # 获取fs连接状态，订阅事件
    con = econ['con']
    if con.connected():
        con.events('plain', "CHANNEL_ANSWER CHANNEL_HANGUP_COMPLETE")
    else:
        jkv['a'] = 0
        logger.error('监控事件fs连接不成功')

    while 1:
        try:
            # eventdata = con.recvEvent()
            eventdata = con.recvEventTimed(100)
            if eventdata:
                ran_str = random.randrange(1, 100000, 1)
                nowtime = datetime.now().strftime("%Y%m%d%H%M%S")
                ran = str(ran_str) + nowtime
                threadjkf = jkfThread(int(ran), ran, eventdata)
                threadjkf.start()
                logger.info('调用分事件处理进程开始：' + ran)

            if jkv['a'] == 0:
                logger.error('fs已断开')
                break
        except Exception as ek:
            logger.error("事件监控上层错误:")
            logger.error(ek)
            logger.error(traceback.format_exc())


''' *** 监控事件处理函数，处理事件jiankf ***'''


def jiankf(evdata):
    try:
        # con = econ['con']
        eventdata = evdata
        EventName = eventdata.getHeader("Event-Name")  # 事件名称
        Etime = eventdata.getHeader("Event-Date-Local")  # 事件发生时间
        Uuid = eventdata.getHeader("Caller-Unique-ID")  # 呼叫ID
        chuuid = eventdata.getHeader("Channel-Call-UUID")  # uuida
        quuid = eventdata.getHeader("Unique-ID")  # 事件主uuid
        data2 = eventdata.getHeader("variable_data2")

        logger.info({'监控事件处理开始': EventName})

        '''通话状态变换监控--接听'''
        if EventName == 'CHANNEL_ANSWER' and chuuid in calllist:
            calllist[chuuid]['callstate'] = EventName
            calllist[chuuid]['awtime'] = Etime

        # elif EventName == 'CHANNEL_HANGUP_COMPLETE' and chuuid in calllist:
        #    del calllist[chuuid]  # 挂断事件，执行删除

    except Exception as ek:
        logger.error("查来电排队监控错误:")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return 0


'''km组合函数'''


def kmcall(msgdtct, gwlist, inlyid, caller, called, callip):
    try:
        rtly = {
            "version": "1.0",
            "xtra": {"tindex": msgdtct.get('tindex'), "tlabel": msgdtct.get('tlabel')},
            "routing": "serial",
            "routes": []}
        gwroutes = []

        # 把网关列表把从小到大排序，数字越小优先级越高
        newgw = random_dic(gwlist)  # 把字典里的gw排序随机下，同等权重的可以随机排序，以达到尽量平均
        gwpx = sorted(newgw.items(), key=lambda x: x[1])
        gwl = [k[0] for k in gwpx]

        # for循环组合多个桥接，按规则改写主被叫号码
        for x in gwl:
            if x in outgw:

                gw = outgw[x]
                gcaller = gw['gcaller']
                gcaller1 = gcaller.get(caller)
                gcalled = gw['gcalled']

                ghcaller = ''
                ghcalled = ''
                # 开始改写主叫
                if gcaller1:
                    haomalist = gcaller1[1].split('/')
                    ghcaller = haomalist[gcaller1[0]]
                    # 取用后更新位置为下一个号码
                    outgw[x]['gcaller'][caller][0] = lxqh(haomalist, gcaller1[0])
                else:
                    # 如果有*号的主叫号码改写，在没有匹配到主叫时，选择*的主叫改写，为空时不改写{'99':[2,'183/165']}
                    allg = gcaller.get('*')
                    if allg:
                        haomalist = allg[1].split('/')
                        ghcaller = haomalist[allg[0]]
                        # 取用后更新位置为下一个号码
                        outgw[x]['gcaller']['*'][0] = lxqh(haomalist, allg[0])

                # 开始被叫号码改写
                for g in gcalled:
                    if called.startswith(g):  # 如果被叫改写里与被叫号码可以在前几位匹配上
                        # 按被叫改写前缀位数改写，新前缀加上去掉老前缀位数的被叫号码，组合成新被叫
                        ghcalled = gcalled[g] + called[(len(g)):]

                # 判断经过上面路由，是否有改写，如无改写，用原主叫或被叫，如有就用改写后的
                cer = ''
                ced = ''
                if ghcalled:
                    ced = ghcalled
                else:
                    ced = called
                if ghcaller:
                    cer = ghcaller
                else:
                    cer = caller
                # 判断网关类型，j为静态，d为动态，走不同的模版
                gwtype = gw.get('type')
                gwip = gw.get('gwip')
                gwport = gw.get('gwport')
                urid = 'sip:' + ced + '@' + gwip + ':' + gwport
                urif = 'sip:' + cer + '@'+tdwwip
                urit = 'sip:' + ced + '@' + gwip
                extd = "Remote-Party-ID: " + cer + " <sip:" + cer + "@" + callip + ">;party=calling;screen=yes;privacy=off\r\n"

                rd = {"uri": urid,
                      "headers": {
                          "from": {"display": cer, "uri": urif},
                          "to": {"display": ced, "uri": urit},
                          "extra": extd
                      }
                      }
                gwroutes.append(rd)
            else:
                logger.warning({'无此网关，不做此网关合成': x})
        # 生成最终要返回给km的路由
        if gwroutes:
            rtly['routes'] = gwroutes
        else:
            rtly['routes'] = [{"uri": "9696"}]
        # logger.warning({'km组合后要返回的路由为：': rtly})
        return rtly
    except Exception as e:
        logger.error("km返回组合处理错误:")
        logger.error(e)
        logger.error(traceback.format_exc())
        return 0


'''km事件异步处理函数'''


def kmev(msgdtct):
    s = kmstate['s']
    if msgdtct.get('event') == "sip-routing":
        print('收到路由事件')
        kmcn['n'] = kmcn['n'] + 1
        logger.warning({'现在km收到的呼叫请求是：': kmcn['n']})
        callip = msgdtct.get('ip')
        caller = msgdtct.get('caller')
        called = msgdtct.get('callee')
        calledbf = msgdtct.get('callee')
        callid = msgdtct.get('callid')
        timenow = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))

        rtkly = {
            "version": "1.0",
            "xtra": {"tindex": msgdtct.get('tindex'), "tlabel": msgdtct.get('tlabel')},
            "routing": "serial",
            "routes": [{"uri": "9696"}]}

        # 随机生成异步线程名
        ran_str = random.randrange(1, 100000, 1)
        ran = str(ran_str) + str(int(time.time()))

        # 判断主被叫和ip是否为空，如为空直接返回空
        if caller and called and callip:
            jg, gwlist, lyid = lyauth(callip, caller, called)  # 调用路由验证函数，拿到匹配结果、网关列表、对应的路由id

            # 查询是否需要改写uid
            if jg == 5 and lyid:
                gxuid = inly[lyid].get('gxuid')
                if gxuid:
                    called = dthmd.uidgx(gxuid, called)
            # 如果结果为5，即是通过，并且网关和lyid都有值，就走组合并返回(gwlist, inlyid, caller, called, type)
            if jg == 5 and gwlist and lyid and called:
                kmrely = kmcall(msgdtct, gwlist, lyid, caller, called, callip)  # 调用xml组合函数，返回所要的xml
                if kmrely:
                    logger.warning({'km组合后要返回的路由为：': kmrely})

                    # 在此调用一个新线程，执行增加到并发监控和入库
                    data = {'callid': callid, 'context': '', 'caller': caller, 'called': called,
                            'callip': callip, 'inappid': inly[lyid].get('inappid'),
                            'jg': jg, 'gwlist': gwlist, 'lyid': lyid,
                            'switchname': 'kamailio',
                            'evtime': timenow,
                            'meiti': '', 'gcaller': kmrely['routes'][0]['headers']['from']['display']}
                    threadybcdr = jkcdrThread(int(ran), ran, data)
                    threadybcdr.start()  # 调用cdr入库和入监控线程

                    da = json.dumps(kmrely)
                    dt = da.encode()
                    ds = str(len(da)) + ':' + da + ','
                    s.send(ds.encode('utf-8'))
                else:
                    logger.warning('km生成失败，现返回not')

                    # 在此调用一个新线程，执行增加到并发监控和入库
                    data = {'callid': callid, 'context': '', 'caller': caller, 'called': called,
                            'callip': callip,
                            'jg': jg, 'gwlist': gwlist, 'lyid': lyid,
                            'switchname': 'kamailio',
                            'evtime': timenow,
                            'meiti': '', }
                    threadybcdr = jkcdrThread(int(ran), ran, data)
                    threadybcdr.start()  # 调用cdr入库和入监控线程

                    da = json.dumps(rtkly)
                    dt = da.encode()
                    ds = str(len(da)) + ':' + da + ','
                    s.send(ds.encode('utf-8'))

            else:
                logger.warning({'路由匹配失败或uid改写失败，现返回not,配置的结果为：': jg})

                # 在此调用一个新线程，执行增加到并发监控和入库
                data = {'callid': callid, 'context': '', 'caller': caller, 'called': calledbf,
                        'callip': callip,
                        'jg': jg, 'gwlist': gwlist, 'lyid': lyid,
                        'switchname': 'kamailio',
                        'evtime': timenow,
                        'meiti': '', }
                threadybcdr = jkcdrThread(int(ran), ran, data)
                threadybcdr.start()  # 调用cdr入库和入监控线程

                da = json.dumps(rtkly)
                dt = da.encode()
                ds = str(len(da)) + ':' + da + ','
                s.send(ds.encode('utf-8'))
        else:
            logger.warning({'主被叫或IP获取不全，不做路由验证：': callip, 'caller': caller, 'called': called})

            # 在此调用一个新线程，执行增加到并发监控和入库
            data = {'callid': callid, 'context': '', 'caller': caller, 'called': called,
                    'callip': callip,
                    'jg': 0, 'gwlist': '', 'lyid': '',
                    'switchname': 'kamailio',
                    'evtime': timenow,
                    'meiti': '', }
            threadybcdr = jkcdrThread(int(ran), ran, data)
            threadybcdr.start()  # 调用cdr入库和入监控线程

            da = json.dumps(rtkly)
            dt = da.encode()
            ds = str(len(da)) + ':' + da + ','
            s.send(ds.encode('utf-8'))
    elif msgdtct.get('event') == "bye":
        print('收到挂断事件', msgdtct)


'''***kmsoc事件实时监控更新***'''


def kmevt(jk):
    # 创建 socket 对象
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    s.setblocking(False)  # 非阻塞模式
    kmstate['s'] = s  # 连接状态存到字典
    ks['a'] = 1
    kms = ks['a']
    print('soc已连接')

    if kms:
        print('开始接收kmsoc事件')
        # 接收小于 1024 字节的数据
        while 1:
            try:
                infds, outfds, errfds = select.select([s, ], [], [], 5)
                # print(infds, outfds, errfds)
                if infds:
                    try:
                        msg = s.recv(1024)
                        print(msg)
                        # print('dws', str(msg))
                        if str(msg) == "b''":
                            print('soc连接断开了')
                            ks['a'] = 0
                            break
                        else:
                            print('soc连接正常')
                        if msg:
                            mstr = msg.decode('utf-8')
                            msd = mstr[(mstr.rfind('{')):-1]
                            msgdtct = eval(msd)

                            '''调用线程，异步处理事件，返回路由'''
                            ran_str = random.randrange(1, 100000, 1)  # 随机生成异步线程名
                            ran = str(ran_str) + str(int(time.time()))
                            threadkmev = kmevthd(int(ran), ran, msgdtct)  # 调用km事件处理线程
                            threadkmev.start()  # 启动

                    except Exception as ek:
                        logger.error("kmsoc接收数据错误:")
                        logger.error(ek)
                        logger.error(traceback.format_exc())

                time.sleep(0.01)
            except Exception as ek:
                logger.error("kmsoc连接错误:")
                logger.error(ek)
                logger.error(traceback.format_exc())
    else:
        ks['a'] = 0
        logger.error('监控事件kmsoc连接不成功')


'''线程实例创建、启动'''

# 创建新线程

try:
    threadxwj = xwjThread(1, "Thread-1", 1)
    # threadkm = kmThread(2, "kmThread-2", 2)
    threadjk = jkThread(3, "Thread-3", 3)
    threadfs = fsThread(4, "Thread-4", 4)
    # threadtit = titThread(6, "Thread-6", 6)
    # 开启线程

    threadxwj.start()
    # threadkm.start()
    threadjk.start()
    threadfs.start()
    # threadtit.start()

except Exception as e:
    logger.error("主线程退出错误原因日志:")
    logger.error(e)
    logger.error(traceback.format_exc())

'''
以下为api接口pass平台部分
'''

app = Flask(__name__)
CORS(app, supports_credentials=True)  # 全局跨域

'''md5加密函数'''


def md5cd(data):
    m = hashlib.md5(data.encode(encoding='utf-8'))  # 参数必须是byte类型，否则报Unicode-objects must be encoded before hashing错误
    return m.hexdigest().upper()


'''验证token和时效性'''


def tokenyz(name, tok):
    token5 = agent_tk[name]['token']
    if tok == token5 and time.time() <= int(agent_tk[name]['time']):
        return 1
    else:
        return 0


'''用户api请求安全验证函数'''


def apiyz(myjson, token):
    logger.error(myjson)
    sig = myjson.get("sig")
    name = myjson.get("name")
    if name in agent_tk:  # and tokenyz(name, token) == 1:
        timec = myjson.get("time")
        tk = md5cd(zypw + name + timec)
        tmnow = int(time.time())
        tm = (tmnow - int(timec))
        print(tk)
        if sig == tk and tm <= 7200:
            return 200
        else:
            return {'msg': 'error', 'code': '151', 'data': 'sig验证不成功，超时或不正确'}
    else:
        return {'msg': 'error', 'code': '403', 'data': 'token不存在或已过期'}


'''
异地登录执行线程函数
'''


class ydThread(threading.Thread):
    def __init__(self, threadID, name, data):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.data = data

    def run(self):
        try:
            logger.info("开始异地处理线程：" + self.name)
            ydlogin(self.data[0], self.data[1], self.data[2])
        except Exception as e:
            logger.error("异地处理错误:")
            logger.error(e)
            logger.error(traceback.format_exc())


'''异地登录email提醒'''


def ydlogin(ipdata, appid, name):
    yd = zybbx.ipgsd(ipdata)
    cydq = zyappid[appid]['attribution']
    emaillist = zyappid[appid]['umail']
    if yd:
        sheng = yd.get('sheng')
        shi = yd.get('shi')
        if shi != 'gw':
            if cydq and emaillist:
                if (sheng in cydq) or (shi in cydq):
                    logger.error('正常登录')
                else:
                    maillist = emaillist.split(',')
                    zt = '座席' + name + '异地登录提醒'
                    text = '座席：' + name + '异地登录提醒，此次登录的IP为' + ipdata + ',归属地为：' + sheng + shi
                    zybbx.zyemail(maillist, zt, text)  # 发送邮件提醒
        elif shi == 'gw':
            mailgw = ['zhangx@zy-yun.com', 'fanzh@zy-yun.com']
            zt = '座席' + name + '异常登录提醒'
            text = '座席：' + name + '异常登录提醒，此次登录的IP为' + ipdata + ',归属地为：' + sheng
            zybbx.zyemail(mailgw, zt, text)  # 发送邮件提醒


'''卓越新架构平台后端接口'''

'''fs-call呼叫路由'''


@app.route("/api/callr/", methods=['POST'])
def callr():
    stm = time.time()
    try:
        print('callr接受数据')
        xmlnub['n'] = xmlnub['n'] + 1
        data = request.get_data()
        # logger.warning(data)
        sd = bytes.decode(data)  # 获取数据，格式是二进制
        ym2 = parse.unquote(sd, 'utf-8')  # 二进制转字符串解码url
        res = dict(parse.parse_qsl(ym2))  # 转为字典
        # print(res)
        context = res.get('Caller-Context')
        caller = res.get('Caller-Caller-ID-Number')
        called = res.get('Caller-Destination-Number')
        calledbf = res.get('Caller-Destination-Number')
        callip = res.get('Caller-Network-Addr')
        callid = res.get('Channel-Call-UUID')
        logger.warning({'context': context, 'caller': caller, 'called': called, 'callip': callip})

        # 随机生成异步线程名
        ran_str = random.randrange(1, 100000, 1)
        ran = str(ran_str) + str(int(time.time()))

        # 判断主被叫和ip是否为空，如为空直接返回空
        if caller and called and callip:
            jg, gwlist, lyid = lyauth(callip, caller, called)  # 调用路由验证函数，拿到匹配结果、网关列表、对应的路由id

            # 查询是否需要改写uid
            if jg == 5 and lyid:
                gxuid = inly[lyid].get('gxuid')
                if gxuid:
                    called = dthmd.uidgx(gxuid, called)

            # 如果结果为5，即是通过，并且网关和lyid都有值，就走组合并返回(gwlist, inlyid, caller, called, type)
            if jg == 5 and gwlist and lyid and called:
                rexml = fscall(gwlist, lyid, caller, called, context)  # 调用xml组合函数，返回所要的xml
                if rexml:
                    logger.warning(rexml)

                    # 在此调用一个新线程，执行增加到并发监控和入库
                    data = {'callid': callid, 'context': context, 'caller': caller, 'called': called, 'callip': callip,
                            'jg': jg, 'gwlist': gwlist, 'lyid': lyid, 'switchname': 'freeswitch', 'inappid': inly[lyid].get('inappid'),
                            'evtime': res.get('Event-Date-Local'), 'meiti': res.get('variable_rtp_use_codec_string'), }
                    threadybcdr = jkcdrThread(int(ran), ran, data)
                    threadybcdr.start()  # 调用cdr入库和入监控线程

                    return rexml, 200, {'Content-Type': 'text/xml; charset=utf-8'}
                else:
                    logger.warning('xml转换失败，现返回not')

                    # 在此调用一个新线程，执行增加到并发监控和入库
                    data = {'callid': callid, 'context': context, 'caller': caller, 'called': called, 'callip': callip,
                            'jg': jg, 'gwlist': gwlist, 'lyid': lyid, 'switchname': 'freeswitch',
                            'evtime': res.get('Event-Date-Local'), 'meiti': res.get('variable_rtp_use_codec_string'), }
                    threadybcdr = jkcdrThread(int(ran), ran, data)
                    threadybcdr.start()  # 调用cdr入库和入监控线程

                    return notf, 200, {'Content-Type': 'text/xml; charset=utf-8'}
            else:
                logger.warning({'路由匹配失败，现返回not,配置的结果为：': jg})

                # 在此调用一个新线程，执行增加到并发监控和入库
                data = {'callid': callid, 'context': context, 'caller': caller, 'called': calledbf, 'callip': callip,
                        'jg': jg, 'gwlist': gwlist, 'lyid': lyid, 'switchname': 'freeswitch',
                        'evtime': res.get('Event-Date-Local'), 'meiti': res.get('variable_rtp_use_codec_string'), }
                threadybcdr = jkcdrThread(int(ran), ran, data)
                threadybcdr.start()  # 调用cdr入库和入监控线程

                return notf, 200, {'Content-Type': 'text/xml; charset=utf-8'}
        else:
            logger.warning({'主被叫或IP获取不全，不做路由验证：': callip, 'caller': caller, 'called': called})

            # 在此调用一个新线程，执行增加到并发监控和入库
            data = {'callid': callid, 'context': context, 'caller': caller, 'called': called, 'callip': callip,
                    'jg': 0, 'gwlist': '', 'lyid': '', 'switchname': 'freeswitch',
                    'evtime': res.get('Event-Date-Local'), 'meiti': res.get('variable_rtp_use_codec_string'), }
            threadybcdr = jkcdrThread(int(ran), ran, data)
            threadybcdr.start()  # 调用cdr入库和入监控线程

            return notf, 200, {'Content-Type': 'text/xml; charset=utf-8'}

    except Exception as e:
        logger.error("接收呼叫请求处理错误:")
        logger.error(e)
        logger.error(traceback.format_exc())
    finally:
        entm = time.time()
        timec = entm - stm
        logger.warning({'返回callxml所用时间:': timec, '现在数量：': xmlnub['n']})


'''路由管理对接网关'''


@app.route("/api/luyou/", methods=['POST', 'GET'])
def luyoud():
    try:
        ip = request.remote_addr
        token1 = request.headers["token"]
        myjson = request.get_json()
        logger.error({'myjson': myjson, '请求的IP': ip})
        apijg = apiyz(myjson, token1)
        if apijg == 200:
            name = myjson.get("name")
            appid = myjson.get("appid", '')
            cmd = myjson.get("cmd")
            data = myjson.get("data", {})
            data1 = myjson.get("data1", 'no')
            data2 = myjson.get("data2", 'no')
            curtime1 = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
            if cmd == 'c':  # 查询
                page = myjson.get("page", '1')
                limit = myjson.get("limit", '10')
                lydatac = inly
                # datac = [lydata[l] for l in lydata]

                # 根据字典数据和检索项查所要的数据
                if 'inappid' not in data:
                    data['inappid'] = appid

                datac, zs = dictss.dictss(lydatac, data, page, limit)
                # print(datac)
                # print(type(datac))

                return jsonify(
                    {
                        'msg': 'success',
                        'code': '200',
                        'data': {
                            'nub': zs,
                            'page': page,
                            'limit': limit,
                            'data1': data1,
                            'data2': data2,
                            'list': datac,
                        }
                    }
                )
            elif cmd == 'z':  # 增加
                lyid = myjson.get('lyid')
                newdata = data
                # print(type(newdata))
                # newdata['fromip'] = eval(data['fromip'])
                # print(type(data['fromip']))
                # newdata['caller'] = eval(data['caller'])
                # print(type(data['fromip']))
                # newdata['gwlist'] = eval(data['gwlist'])
                # print(type(data['fromip']))
                if lyid not in inly and data and lyid:
                    inly[lyid] = newdata
                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )

            elif cmd == 'g':  # 修改
                lyid = myjson.get('lyid')
                newdata = data
                print(type(newdata))
                '''

                fromip = data['fromip']
                newdata['fromip'] = eval(fromip)
                print(type(newdata['fromip']))
                newdata['caller'] = eval(data['caller'])
                print(type(newdata['caller']))
                newdata['gwlist'] = eval(data['gwlist'])
                print(type(newdata['gwlist']))
                print(newdata)
                '''
                if lyid in inly and data and lyid:
                    inly[lyid] = newdata
                    # print(inly)

                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )

            elif cmd == 's':  # 删除
                lyid = myjson.get('lyid')
                if lyid in inly and lyid:
                    del inly[lyid]

                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )

            else:
                logger.error('命令错误')
                return jsonify(
                    {
                        'msg': 'error',
                        'code': '152',
                        'data': {
                            'data1': data1,
                            'data2': data2,
                            'list': [{}]

                        }
                    }
                )

        else:
            logger.error('验证不成功，超时或不正确')
            return jsonify(apijg)

    except Exception as ek:
        logger.error("非token错误:")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return jsonify(
            {'msg': 'error', 'code': '500', 'data': ''})


'''路由管理对接网关'''


@app.route("/api/outgw/", methods=['POST', 'GET'])
def outgwapi():
    try:
        ip = request.remote_addr
        token1 = request.headers["token"]
        myjson = request.get_json()
        logger.error({'myjson': myjson, '请求的IP': ip})
        apijg = apiyz(myjson, token1)
        if apijg == 200:
            name = myjson.get("name")
            appid = myjson.get("appid", 'no')
            cmd = myjson.get("cmd")
            data = myjson.get("data", {})
            data1 = myjson.get("data1", 'no')
            data2 = myjson.get("data2", 'no')
            curtime1 = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
            if cmd == 'c':  # 查询
                page = myjson.get("page", '1')
                limit = myjson.get("limit", '10')
                gwdata = outgw
                # 根据字典数据和检索项查所要的数据
                if 'outappid' not in data:
                    data['outappid'] = appid

                datac, zs = dictss.dictss(gwdata, data, page, limit)

                return jsonify(
                    {
                        'msg': 'success',
                        'code': '200',
                        'data': {
                            'nub': zs,
                            'page': page,
                            'limit': limit,
                            'data1': data1,
                            'data2': data2,
                            'list': datac,
                        }
                    }
                )
            elif cmd == 'z':  # 增加
                gwid = myjson.get('gwid')
                # data['gcaller'] = eval(data['gcaller'])
                # data['gcalled'] = eval(data['gcalled'])
                if gwid not in outgw and data and gwid:
                    outgw[gwid] = data
                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )
                else:
                    return jsonify(
                        {'msg': 'error', 'code': '153', 'data': '已有此id'})

            elif cmd == 'g':  # 修改
                gwid = myjson.get('gwid')
                # data['gcaller'] = eval(data['gcaller'])
                # data['gcalled'] = eval(data['gcalled'])
                if gwid in outgw and data and gwid:
                    outgw[gwid] = data
                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )
                else:
                    return jsonify(
                        {'msg': 'error', 'code': '153', 'data': '无此id或data无数据'})

            elif cmd == 's':  # 删除
                gwid = myjson.get('gwid')
                if gwid in outgw and gwid:
                    del outgw[gwid]

                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )
                else:
                    return jsonify(
                        {'msg': 'error', 'code': '153', 'data': '无此id'})

            else:
                logger.error('命令错误')
                return jsonify(
                    {
                        'msg': 'error',
                        'code': '152',
                        'data': {
                            'data1': data1,
                            'data2': data2,
                            'list': [{}]

                        }
                    }
                )

        else:
            logger.error('验证不成功，超时或不正确')
            return jsonify(apijg)

    except Exception as ek:
        logger.error("非token错误:")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return jsonify(
            {'msg': 'error', 'code': '500', 'data': ''})


'''当前通话监控'''


@app.route("/api/calllist/", methods=['POST', 'GET'])
def calllistapi():
    try:
        ip = request.remote_addr
        token1 = request.headers["token"]
        myjson = request.get_json()
        logger.error({'myjson': myjson, '请求的IP': ip})
        apijg = apiyz(myjson, token1)
        if apijg == 200:
            name = myjson.get("name")
            appid = myjson.get("appid", 'no')
            cmd = myjson.get("cmd")
            data = myjson.get("data", 'no')
            data1 = myjson.get("data1", 'no')
            data2 = myjson.get("data2", 'no')
            curtime1 = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
            if cmd == 'c':  # 查询
                page = myjson.get("page", '1')
                limit = myjson.get("limit", '10')
                calldata = calllist
                # datac = [calldata[c] for c in calldata]
                datac = []
                for c in calldata:
                    # calldata[c]['gwlist'] = str(calldata[c]['gwlist'])
                    datac.append(calldata[c])

                return jsonify(
                    {
                        'msg': 'success',
                        'code': '200',
                        'data': {
                            'nub': len(datac),
                            'page': page,
                            'limit': limit,
                            'data1': data1,
                            'data2': data2,
                            'list': datac,
                        }
                    }
                )

            elif cmd == 'g':  # 修改
                callid = myjson.get('callid')
                if callid in calllist and data:
                    calllist[callid] = data
                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )

            elif cmd == 's':  # 删除
                callid = myjson.get('callid')
                if callid in calllist and callid:
                    del calllist[callid]

                    return jsonify(
                        {
                            'msg': 'success',
                            'code': '200',
                            'data': {}
                        }
                    )

            else:
                logger.error('命令错误')
                return jsonify(
                    {
                        'msg': 'error',
                        'code': '152',
                        'data': {
                            'data1': data1,
                            'data2': data2,
                            'list': [{}]

                        }
                    }
                )

        else:
            logger.error('验证不成功，超时或不正确')
            return jsonify(apijg)

    except Exception as ek:
        logger.error("非token错误:")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return jsonify(
            {'msg': 'error', 'code': '500', 'data': ''})


'''td记录'''


@app.route("/api/tdrecord/", methods=['POST', 'GET'])
def tdrecordapi():
    try:
        ip = request.remote_addr
        token1 = request.headers["token"]
        myjson = request.get_json()
        logger.error({'myjson': myjson, '请求的IP': ip})
        apijg = apiyz(myjson, token1)
        if apijg == 200:
            name = myjson.get("name")
            appid = myjson.get("appid", 'no')
            cmd = myjson.get("cmd")
            data = myjson.get("data", 'no')
            data1 = myjson.get("data1", 'no')
            data2 = myjson.get("data2", 'no')

            if cmd == 'c':  # 查询
                page = myjson.get("page", '1')
                limit = myjson.get("limit", '10')

                nub = 0
                n = int(limit)
                m = (int(page) - 1) * n

                cx = "1=1"
                # if appid != 'zysys01':
                #     cx = "inappid = '" + appid + "' "
                if data != 'no':
                    for c in data:
                        if data[c]:
                            if cx:
                                if c in ['startTime', 'endTime', 'called']:
                                    if c == 'startTime':
                                        cx = cx + " and starttime >='" + data[c] + " 00:00:00'"
                                    elif c == 'endTime':
                                        cx = cx + " and starttime <='" + data[c] + " 23:59:59'"
                                    else:
                                        cx = cx + " and called like '%" + str(data[c]) + "'"
                                else:
                                    cx = cx + " and " + c + "= '" + str(data[c]) + "'"
                            else:
                                cx = c + "= '" + str(data[c]) + "'"
                        else:
                            cx = cx
                else:
                    cx = cx

                fy = cx + " order by starttime desc limit 100"
                sql = "select * from tdcdr where " + fy
                datac = zysql(sql, 'crmdatacc')
                return jsonify(
                    {
                        'msg': 'success',
                        'code': '200',
                        'data': {
                            'nub': 100,
                            'page': page,
                            'limit': limit,
                            'data1': data1,
                            'data2': data2,
                            'list': datac,
                        }
                    }
                )
            else:
                logger.error('命令错误')
                return jsonify(
                    {
                        'msg': 'error',
                        'code': '152',
                        'data': {
                            'data1': data1,
                            'data2': data2,
                            'list': [{}]

                        }
                    }
                )

        else:
            logger.error('验证不成功，超时或不正确')
            return jsonify(apijg)

    except Exception as ek:
        logger.error("非token错误:")
        logger.error(ek)
        logger.error(traceback.format_exc())
        return jsonify(
            {'msg': 'error', 'code': '500', 'data': ''})


'''cdr异步处理线程'''


class cdrt(threading.Thread):
    def __init__(self, threadID, name, data):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.data = data

    def run(self):
        try:
            logger.info("开始异步监控和入库fs-cdr线程：" + self.name)
            cdrcl(self.data)
        except Exception as e:
            logger.error("异步监控和入库线程错误:")
            logger.error(e)
            logger.error(traceback.format_exc())


'''cdr组件函数'''


# 计费并入库函数,传入appid、通话时长（秒）、费用单价字段名（便于用appid取到费用单价，不同的呼叫方式单价不一样）

def cdrjf(appid, thsc, userTravel):
    sqlname = 'crmdatacc'  # 数据库名称
    datenow = time.strftime('%Y-%m-%d', time.localtime(time.time()))  # 获取当前日期，用于计费判断
    if zyappid[appid]['jfdate'] == datenow:
        # 相等说明已经插入过了，直接修改
        sql1 = "update chargelist set cje1=cje1+" + str(userTravel) + ",tcnum=tcnum+" + str(
            thsc) + " where appid='" + appid + "' and cdate='" + datenow + "' and cremark='话费结算'"
        zysqls(sql1, sqlname)
    else:
        # 不相等，说明今天第一次,先修改字典和数据库jfdate的值为现在的datenow，然后再新增
        zyappid[appid]['jfdate'] = datenow
        sql2 = "insert into chargelist(appid,cje1,cdate,tcnum,cremark) values('" + appid + "'," + str(
            userTravel) + ",'" + datenow + "'," + str(thsc) + ",'话费结算')"
        zysqls(sql2, sqlname)

    sql = "update company set uYe=uYe-" + str(userTravel) + ",tcNum=tcNum+" + str(
        thsc) + ",jfdate='" + datenow + "' where appid='" + appid + "'"
    zysqls(sql, sqlname)


# 入库通话记录函数,传入要入库的数据、通话时长、username

def cdrsql(rdata, thsc, username, userTravel):
    xz = 'ucode,userTravel,charge'
    vs = "'" + username + "'," + str(userTravel) + "," + str(thsc) + ""
    for z in rdata:
        if xz:
            xz = xz + ',' + z
            vs = vs + ',' + '"' + str(rdata[z]) + '"'
        else:
            xz = z
            vs = '"' + str(rdata[z]) + '"'
    key = "(" + xz + ")"

    v = "(" + vs + ")"
    sql = "insert into calldata" + str(key) + " values " + str(v)
    logger.error('-----sql:' + sql)
    # 执行sql入库，调mysql函数
    zysqls(sql, 'crmdatacc')  # 执行sql


'''话单处理函数，与话单接收分开，异步处理'''


def cdrcl(ddj):
    dv = ddj['cdr']['variables']
    trunk_number = dv.get('trunk_number')
    calltype = dv.get('callType')
    direction = dv.get('direction')
    logger.error({'calltype:': calltype, 'directiom:': direction})
    datenow = time.strftime('%Y-%m-%d', time.localtime(time.time()))  # 获取当前日期，用于计费判断

    logger.warning('收到挂断话单')
    rdata = tdzycdr.sipcall(ddj)  # 话单处理，返回json，直接走话单的直拨处理，因无其它呼叫方式
    # logger.warning(rdata)
    if rdata:
        sqlname = 'crmdatacc'
        callid = rdata.get('callid')
        jttime = int(rdata.get('billsec'))  # 通话时长--计费时长
        alltime = rdata.get('totaltime')
        answerstamp = rdata.get('answer_stamp')
        endtime = rdata.get('endtime')
        lyid = rdata.get('lyid')
        lyname = ''
        inappid = ''
        if lyid in inly:
            lyname = inly[lyid]['lyname']
            inappid = inly[lyid]['inappid']
        gwid = rdata.get('gwid')
        gwname = ''
        outappid = ''
        if gwid in outgw:
            gwname = outgw[gwid]['gwname']
            outappid = outgw[gwid]['outappid']
        sipcode = rdata.get('hangup_cause')
        filename = rdata.get('filename')
        gdf = rdata.get('hangup_disposition')
        thsc = 0
        if jttime > 0:
            thsc = int(jttime / 60) + 1  # 计费时长
        try:
            if callid and callid in calllist:
                # logger.error('查到监控现在删除')
                del calllist[callid]  # 因通话已挂断，删除监控中对应的通话
        except Exception as e:
            logger.error("删除监控错误:")
            logger.error(e)
            logger.error(traceback.format_exc())
        # 下面处理sql入库流程,fzhsql
        # 修改td记录
        sql = "update tdcdr set awtime='" + answerstamp + "',endtime='" + endtime + "',jttime=" + str(
            jttime) + ",lyname='" + lyname + "',inappid='" + inappid + "',gwname='" + gwname + "',outappid='" + str(
            outappid) + "',jftime=" + str(
            thsc) + ",sipcode='" + sipcode + "',record='" + filename + "',gdf='" + gdf + "',alltime=" + str(
            alltime) + ",lyid='" + lyid + "',outgw='" + gwid + "' where callid='" + callid + "'"
        # logger.warning('sql:' + sql)
        zysqls(sql, sqlname)

        # 如果appid在列表里，要推送到老系统计费
        if inappid in ['h61gy3']:
            cdrurl = conf.get('tdjfcdr')  # 需要接收的话单地址
            rdata['cdrurl'] = cdrurl
            rdata['lyname'] = lyname
            rdata['inappid'] = inappid
            tdzycdr.cdrt(rdata)  # 执行推送

        # 调用hmd记录函数，处理呼叫
        hm = rdata.get('called')
        if lyid and hm:
            hmdidj = inly[lyid]['hmdid']
            if hm and hmdidj:
                hmdcall.hmdsc({'called': hm, 'state': rdata.get('state'), 'endtime': endtime}, hmdgz324, hmdidj)
    else:
        logger.warning('处理异常')


'''cdr话单回调接口'''


@app.route("/api/cdr/", methods=['POST'])
def cdr():
    try:
        data = request.get_data()
        cdrn['n'] = cdrn['n'] + 1
        logger.warning({'现收到cdr的数量为：': cdrn['n']})
        sd = bytes.decode(data)  # 获取数据，格式是二进制
        ym2 = urllib.parse.unquote(sd, 'utf-8')  # 二进制转字符串解码url
        # print('data=', data)
        # print('sd=', sd)
        # print('ym2=', ym2)
        dd = ym2[4:]  # 截取cdr=
        # print('ddtype=',type(dd))
        # print('dd=', dd)
        ddj = xmltodict.parse(dd)
        # callerpro = ddj['cdr']['callflow']['caller_profile']
        logger.warning('收到cdr')
        # 随机生成异步线程名
        ran_str = random.randrange(1, 100000, 1)
        ran = str(ran_str) + str(int(time.time()))
        tcdr = cdrt(int(ran), ran, ddj)
        tcdr.start()  # 调用cdr入库和入监控线程
        return jsonify(
            {'msg': 'success', 'code': '200', 'data': ''})
    except Exception as e:
        logger.error("接收cdr错误:")
        logger.error(e)
        logger.error(traceback.format_exc())


'''获取IP和端口，启动api平台'''

IP = '0.0.0.0'
PORT = 56890

logger.info('***启动卓越api平台 ip:0.0.0.0  port:56890  ***')

# 启动web函数

if __name__ == '__main__':
    server = pywsgi.WSGIServer((IP, PORT), app)
    server.serve_forever()
